﻿#include <QApplication>
#include <QWidget>

#include <QDebug>
#include <QThread>

//在Windows平台去掉黑窗口：
#pragma comment(linker,"/subSystem:console /entry:mainCRTStartup")
//如果想让黑窗口出来，就把Windows 改成 console


/*
第一种：QThread::create:
	创建一个新的QThread对象，该对象将执行带有参数args的函数f。
	新线程没有启动——它必须通过显式调用start()来启动。这允许您连接到它的信号，将QObjects移动到线程，选择新线程的优先级等等。函数f将在新线程中被调用。

*/
//全局函数：
void print()
{
	//打印当前线程的id：
	qInfo() << "hello global fun" << __FUNCSIG__ << "线程id：" << QThread::currentThread();
}


struct Foo
{
	void show()
	{
		qInfo() << "成员函数";
	}

	static void static_show()
	{
		qInfo() << "静态函数";
	}
};

int main1(int argc, char* argv[])
{
	QApplication a(argc, argv);


	//1.创建线程：
	auto global_thread = QThread::create(print);

	//启动线程：
	global_thread->start();

	qInfo() << "hello main " << __FUNCSIG__ << "线程id：" << QThread::currentThread();
	/*
		hello main  int __cdecl main(int,char *[]) QThread(0x15e7b0e2730)
		hello global fun void __cdecl print(void) QThread(0x15e7b129960)

		可以看出，main和print两个是在不同的线程中；

	*/


	//线程自己创建了，一定还需要自己释放：		通过信号来释放：
	QObject::connect(global_thread, &QThread::finished, [=]
		{
			global_thread->deleteLater();
			qInfo() << "finished global_thread";
		});







	//2.使用成员函数作为线程的入口函数：
			//使用成员函数作为线程的入口必须	要创建对象，必须有对象才能够调用：
	Foo foo;
	auto mem_thread = QThread::create(&Foo::show, std::ref(foo));

	//启动线程：
	mem_thread->start();

	//打印线程id：
	qInfo() << "mem_thread 线程id：" << mem_thread->currentThreadId();

	//自己释放线程：
	QObject::connect(mem_thread, &QThread::finished, [=]
		{
			mem_thread->deleteLater();
			qInfo() << "finished mem_thread";
		});




	//使用静态成员函数：
	auto static_thread = QThread::create(&Foo::static_show);

	//启动线程：
	static_thread->start();

	//打印线程id：
	qInfo() << "static_thread 线程id：" << static_thread->currentThreadId();

	//自己释放线程：
	QObject::connect(static_thread, &QThread::finished, [=]
		{
			static_thread->deleteLater();
			qInfo() << "finished static_thread";
		});





	//3/使用lamdba作为线程的入口函数：
	auto lambda_thread = QThread::create([]
		{
			qInfo() << "hello lambda" << __FUNCSIG__ << "线程id：" << QThread::currentThreadId();
		});

	//启动线程：
	lambda_thread->start();;

	//自己释放线程：
	QObject::connect(lambda_thread, &QThread::finished, [=]
		{
			lambda_thread->deleteLater();
			qInfo() << "finished lambda_thread";
		});




	return a.exec();
}


/*
第二种：继承QThread,重写run
	QThread类中有一个virtual函数QThread::run()，要创建一个新的线程，我们只需定义一个MyThread类，让其继承QThread，然后重新实现QThread::run()。
	把需要在线程中执行的代码全部塞到run函数中。
	run函数是线程的起始点。 在调用start()之后，新创建的线程调用这个函数。 默认实现只是调用exec()，用来处理线程中的事件。

*/


class MyThread :public QThread
{
public:
	using QThread::QThread;

protected:
	void run() override
	{
		for (size_t i = 0; i < 10; i++)
		{
			qInfo() << "hello Qt!!!" << i << QThread::currentThreadId();

			/*
			输出结果：
				hello Qt!!! 0 0x50fc
				hello Qt!!! 1 0x50fc
				hello Qt!!! 2 0x50fc
				hello Qt!!! 3 0x50fc
				hello Qt!!! 4 0x50fc
				hello Qt!!! 5 0x50fc
				hello Qt!!! 6 0x50fc
				hello Qt!!! 7 0x50fc
				hello Qt!!! 8 0x50fc
				hello Qt!!! 9 0x50fc
			
			*/

			QThread::msleep(50);

		}
	}


};

int main2(int argc, char* argv[])
{
	QApplication a(argc, argv);

	MyThread thread;
	thread.start();

	return a.exec();
}


/*
第三种：QObject::moveToThread			官方推荐的做法!!!
	追溯历史，在 Qt 4.4 版本以前的 QThread 类是个抽象类，要想编写多线程代码唯一的做法就是继承 QThread 类，然后重写run函数。但是之后的版本中，
	Qt 库完善了线程的亲和性以及信号槽机制，我们有了更为优雅的使用线程的方式，即 QObject::moveToThread()。这也是官方推荐的做法，
	遗憾的是大部分教程没有跟上技术的进步，依然采用 run() 这种腐朽的方式来编写多线程程序。

	之所以 Qt 官方对 QThread 类进行了大刀阔斧地改革，我认为这是想让多线程编程更加符合 C++ 语言的「面向对象」特性。继承的本意是扩展基类的功能，
	所以继承 QThread 并把耗时操作代码塞入 run() 函数中的做法怎么看都感觉不伦不类。

应该把耗时代码放哪里？
	暂时不考虑多线程，先思考这样一个问题：想想我们平时会把耗时操作代码放在哪里？一个类中。那么有了多线程后，难道我们要把这段代码从类中剥离出来单独放到某个地方吗？
	显然这是很糟糕的做法(比如，用于下载文件、查询数据库，或者做任何其他操作的代码都不应该被加入到QThread的子类中；		它应该被封装在它自己的对象中。)

	假设现在我们有个 QObject 的子类 Worker，这个类有个成员函数 doSomething()，该函数中运行的代码非常耗时。此时我要做的就是将这个类对象“移动”到新线程里，
	这样 Worker 的所有成员函数就可以在新线程中运行了。那么如何触发这些函数的运行呢？			信号槽。
	在主线程里需要有个		signal	 信号来关联并触发 Worker 的成员函数，与此同时 Worker 类中也应该有个	signal	 信号用于向外界发送运行的结果。
	这样思路就清晰了，Worker 类需要有个槽函数用于执行外界的命令，还需要有个信号来向外界发送结果。

*/

#include <QKeyEvent>


//一般这个类是在别的类里面调用的：
#if 0
class Worker : public QObject		//必须简介或直接继承自QObject
{
	//Q_OBJECT

signals:
	void resultReady(const QString& str);

public:
	void doSomething()
	{
		//假设有一个非常耗时的操作：
		QThread::sleep(3);

		qInfo() << "I`m worker thread" << QThread::currentThreadId();

		//发送信号：
		emit resultReady("hello I`m worker of doSomething");

	}
};


class Controller : public QWidget
{
	//Q_OBJECT

signals:
	void startThread();

private:
	QThread* thread;


public:
	Controller()
	{
		Worker* worker = new Worker;		//一定不能指定父对象

		thread = new QThread(this);

		//1.把	worker	移动到线程中：
		worker->moveToThread(thread);

		//2.连接信号：
#if 1
		connect(this, &Controller::startThread, worker, &Worker::doSomething);
#else
		connect(this, &Controller::startThread, worker, [=]
			{
				worker->doSomething();
			});
#endif

		//3.开启线程：(线程处理函数，并没有执行）
		thread->start();
		//使用这种方式启动，线程就不会结束；需要自己手动释放

		//如果不使用下面的 信号的发射语句	是无法触发信号的；
		emit startThread();			//用于触发名为 startThread 的信号。


		connect(thread, &QThread::started, this, [=]
			{
				qInfo() << "finished thread";
			});
		connect(thread, &QThread::finished, this, [=]
			{
				qInfo() << "finished thread";
			});

		connect(worker, &Worker::resultReady, this, &Controller::handleResultReady,
			Qt::ConnectionType::DirectConnection	//设置槽函数在信号发送的线程中执行
			//Qt::ConnectionType::QueuedConnection	//设置槽函数在对象的依附线程中执行，worker在主线程中创建的，所以这样设置应该在主线程中执行
		);

		//上面的两个		connect的第五个参数		面试可能会问；

	}
	~Controller()
	{
		if (thread->isRunning())
		{
			thread->quit();
			thread->wait();
		}

	}
public slots:
	void handleResultReady(const QString& str)
	{
		qInfo() << str << __FUNCSIG__ << QThread::currentThreadId();
		//一般情况下 ，这个函数是在主线程（main）中执行的；可以在connect中设置第五个参数来移动到别的线程中；
	}

protected:
	//使用按键控制	事件响应来	释放线程：
	void keyPressEvent(QKeyEvent* ev)override
	{
		if (ev->key() == Qt::Key::Key_Up)
		{
			thread->quit();
			thread->wait();
		}
	}

};

int main3(int argc, char* argv[])
{
	QApplication a(argc, argv);


	Controller c;
	c.show();

	qInfo() << "hello main" << QThread::currentThreadId();


	return a.exec();
}

#endif


/*
启动线程前的准备工作
	开多少个线程比较合适？
		说“开线程”其实是不准确的，这种事儿只有操作系统才能做，我们所能做的是管理其中一个线程。无论是 QThread thread 还是 QThread *thread，
		创建出来的对象仅仅是作为操作系统线程的接口，用这个接口可以对线程进行一些操作。虽然这样说不准确，但下文我们仍以“开线程”的说法，只是为了表述方便。

		我们来思考这样一个问题：“线程数是不是越大越好”？显然不是，“开”一千个线程是没有意义的。线程的切换是要消耗系统资源的，频繁的切换线程会使性能降低。
		线程太少的话又不能完全发挥 CPU 的性能。一般后端服务器都会设置最大工作线程数，不同的架构师有着不同的经验，有些业务设置为 CPU 逻辑核心数的4倍，有的甚至达到32倍。


		在 *Venkat Subramaniam* 博士的《[Programming Concurrency on the JVM]
		(https://link.zhihu.com/?target=https%3A//book.douban.com/subject/6039359/)》这本书中提到关于最优线程数的计算，
		即线程数量 = 可用核心数/(1 - 阻塞系数)。可用核心数就是所有逻辑 CPU 的总数，这可以用 QThread::idealThreadCount() 静态函数获取，
		比如双核四线程的 CPU 的返回值就是4。
		但是阻塞系数比较难计算，这需要用一些性能分析工具来辅助计算。如果只是粗浅的计算下线程数，最简单的办法就是 CPU 核心数 * 2 + 2。
		更为精细的找到最优线程数需要不断的调整线程数量来观察系统的负载情况。


设置栈大小
	每个线程都有自己的栈，彼此独立，由编译器分配。一般在 Windows 的栈大小为2M，在 Linux 下是8M。
	Qt 提供了获取以及设置栈空间大小的函数：stackSize()、setStackSize(uint stackSize)。其中 stackSize() 函数不是返回当前所在线程的栈大小，
	而是获取用 stackSize() 函数手动设置的栈大小。
	如果是用编译器默认的栈大小，该函数返回0，这一点需要注意。为什么要设置栈的大小？这是因为有时候我们的局部变量很大（常见于数组），
	当超过编译器默认大小时程序就会因为栈溢出而报错，这时候就需要手动设置栈大小了。



*/

int main4(int argc, char* argv[])
{
	QApplication a(argc, argv);

	//打印总线程数：
	qInfo() << QThread::idealThreadCount();		//16

	//建议的线程数量：
	auto idealcount = QThread::idealThreadCount();		//16



	int threadNum = (idealcount) ? (idealcount) : (14 + 2);
	for (int i = 0; i < threadNum; i++)
	{
		//开启线程...
	}


	auto thread = QThread::create([]
		{
			char arr[1024 * 1024 * 3];	//单纯这样写，会出现堆栈溢出
		});

	//再加上这个，就不会堆栈溢出了：	要在	启动线程	之前设置出来：
	thread->setStackSize(1024 * 1024 * 3);

	thread->start();

#if 0
	thread->terminate();
	/*
	强制终止线程，调用该函数后所有处于等待状态的线程都会被唤醒。该函数是异步的，也就是说调用该函数后虽然获得了返回值，但此时线程依然可能在运行。
	因此，一般是在后面跟上 wait() 函数来保证线程已退出**。当然强制是很暴力的行为，有可能会造成局部变量得不到清理，或者无法解锁互斥关系，种种行为都是很危险的，
	除非必要时才会使用该函数。

	*/
#else
	thread->quit();
#endif
	thread->wait();		//等线程清理完毕；不等可能就会有问题


	return a.exec();
}




/*
线程的中断标志位
	Qt 为每一个线程都设置了一个布尔变量用来标记当前线程的终端状态，用 isInterruptionRequested() 函数来获取，用 requestInterruption() 函数来设置中断标记。
	这个标记不是给操作系统看的，
	而是给用户写的代码中进行判断。也就是说调用 requestInterruption() 函数并不能中断线程，需要我们自己的代码去判断。这有什么用处呢？

	这种设计可以让我们自助的中断线程，而不是由操作系统强制中断。经常我们会在新线程上运行无限循环的代码，在代码中加上判断中断标志位可以让我们随时跳出循环。
	好处就是给了我们程序更大的灵活性。
*/

class SThread : public QThread
{
public:
	bool isRun = true;

	void run()override
	{
		//这个时候	线程就不会结束了；
		//while(1){ }



		//中断结束线程：
#if 0
		for (size_t i = 0; isRun; i++)
		{
			qInfo() << "run>" << i;

			//这样通过条件判断	线程的结束：
			if (10000 == i)
				isRun = false;
		}

#else		//Qt中也有这样的函数操作：		无非就是封装

		//isInterruptionRequested——中断请求

		for (size_t i = 0; !isInterruptionRequested()/*加上！就是没有中断请求*/; i++)
		{
			qInfo() << "run>" << i;

			if (10000 == i)
			{
				//请求中断：
				requestInterruption();
			}

		}
#endif




	}


};

int main5(int argc, char* argv[])
{
	QApplication a(argc, argv);


	SThread s;
	s.start();

	QObject::connect(&s, &QThread::finished, []
		{
			qInfo() << "finished sthread";
		});



	return a.exec();
}



/*
线程锁：
	QThreadStorage 类为每个线程提供了独立的数据存储功能，即使在线程中用到全局变量，只要存在 QThreadStorage 中，也不会影响到其他线程。


	在多线程中，是多个线程在一起执行，在下面定义了一个全局变量，在两个线程中都有对该变量的改变，所以哪个线程晚执行 gNum = 88/99; 的操作，gNum就最终的值是多少；
	线程隶属于某一个进程，与进程内的其他线程一起共享这片地址空间”。也就是说全局变量属于公共资源，被所有线程所共享，
	只要一个线程修改了这个全局变量自然就会影响其他线程对该全局变量的访问
	这就是线程比较难以处理的问题，是线程安全问题；



*/
#include <QTHreadStorage>

#if 0
//全局变量：
int gNum = 6;
void fun1()
{
	gNum = 99;
	qInfo() << __FUNCSIG__ << ">	gNum:>" << gNum;
}

void fun2()
{
	gNum = 88;
	qInfo() << __FUNCSIG__ << ">	gNum:>" << gNum;
}
#else

QThreadStorage<int> gNum;
void fun1()
{
	gNum.setLocalData(99);
	qInfo() << __FUNCSIG__ << ">	gNum:>" << gNum.localData();
}

void fun2()
{
	gNum.setLocalData(88);
	qInfo() << __FUNCSIG__ << ">	gNum:>" << gNum.localData();
}

#endif

int main6(int argc, char* argv[])
{
	QApplication a(argc, argv);

	//线程1：
	auto thread1 = QThread::create(fun1);
	thread1->start();
	QObject::connect(thread1, &QThread::finished, [=]
		{
			qInfo() << "finished thread1";
		});

	//线程2：
	auto thread2 = QThread::create(fun2);
	thread2->start();
	QObject::connect(thread2, &QThread::finished, [=]
		{
			qInfo() << "finished thread2";
		});



	return a.exec();
}




/*
子线程不能操作UI:
	GUI框架一般只允许UI线程操作界面组件，Qt也是如此，否则会导致程序崩溃或者出现一些诡异的问题
		子线程只能处理逻辑；
	但我们的应用程序一般是多线程的，势必就涉及到UI线程与子线程的交互。

解决方案:
	下面介绍常用的UI线程与子线程交互方式：
		通过信号与槽
			大体思路：当子线程中需要对ui对象进行操作时，发出一个信号，在与之连接的槽中处理ui操作。信号和槽的连接方式必须是
					BlockingQueuedConnection或QueuedConnection的连接方式连接（这是connect的第五个参数的设定值）



*/
#include <QProgressBar>
#include <QTimer>

//自定义事件类型枚举：
enum CustomEventType
{
	ProgressChanged = QEvent::User	//规定必须从user开始
};

class ProgressChangedEvent :public QEvent
{
public:
	ProgressChangedEvent(int v)
		:QEvent(QEvent::Type(ProgressChanged)/*转成枚举类型*/)
		,value(v)
	{

	}


	int value{ 0 };
};

class Widget :public QWidget
{
	Q_OBJECT

private:
	QProgressBar* progressbar;



	int progressValue{ 0 };

signals:
	void progressChanged(int v);

protected:
	void customEvent(QEvent* ev)override
	{
		if (ev->type() == CustomEventType::ProgressChanged)
		{
			progressbar->setValue(static_cast<ProgressChangedEvent*>(ev)->value);
		}
	}
public:
	Widget(QWidget* parent = nullptr)
		:QWidget(parent)
		, progressbar(new QProgressBar(this))
	{
		//t_timer();

		t_thread_timer();
		connect(this, &Widget::progressChanged, progressbar, &QProgressBar::setValue);


		//方法4的定时器：
		auto timer = new QTimer(this);
		timer->callOnTimeout([=]
			{
				progressbar->setValue(progressValue);
			});

		timer->start(15);
	}
	void t_timer()
	{
		auto timer = new QTimer(this);
		timer->callOnTimeout([=]
			{
				static int v = 0;

				progressbar->setValue(v++);
			});

		timer->start(10);

	}

	void t_thread_timer()
	{

#if 0		//操作不了：
		auto thread = QThread::create([=]
			{
				for (int i = 0; i <= 100; i++)
				{
					QThread::msleep(20);

					progressbar->setValue(i);
				}

			});

		thread->start();
		/*
			这个时候就应该报错了：
			ASSERT failure in QCoreApplication : sendEvent: "Cannot send eventsto objects owned by a different thread.Current thread 0x0x1 8b7c8ce920.Receiver "
			(of type 'QWidget') was created in thread 0x0x1 8b7c880dd0" , file

			可以看出 该线程就不是主线程，

			子线程不能操作UI相关的东西；只能在UI线程（主线程）操作；
		*/
#endif
		//是有方法来操作子线程控制操作ui的：

		auto thread = QThread::create([=]
			{
				for (int i = 0; i <= 100; i++)
				{
					QThread::msleep(20);

					//progressbar->setValue(i);
					
					//子线程控制ui 的4种方法：
						//方法1：通过发送信号来实现子线程控制UI操作：	还需要信号槽；
					//emit progressChanged(i);


						//方法2：	QMetaObject::invokeMethod
					//QMetaObject::invokeMethod(progressbar,/*要调用的类对象*/		 "setValue"/*要调用的函数的函数名*/		,Q_ARG(int,i)/*不能直接传i，要用Q_ARG来包裹*/);


						//方法3：（通过事件去处理的）	自定义事件类型枚举,自定义事件类ProgressChangedEvent，重写customEvent函数：
					//QApplication::postEvent(this, new ProgressChangedEvent(i));

						//方法4：	自定义变量和定时器
					progressValue = i;

				}

			});

		thread->start();

	}

};


int main7(int argc, char* argv[])
{
	QApplication a(argc, argv);

	Widget w;
	w.show();



	return a.exec();
}



/*
QMetaObject::invokeMethod介绍:

	[static] bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, 
	QGenericArgument val0 = QGenericArgument(nullptr), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(),
	QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), 
	QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), 
	QGenericArgument val9 = QGenericArgument())



	调用对象obj上的成员(信号或槽名)。如果成员可以被调用，则返回true。如果没有这样的成员或形参不匹配，则返回false。
	调用可以是同步的，也可以是异步的，这取决于类型:
		如果type是Qt::DirectConnection，成员将立即被调用。
		如果type为Qt::QueuedConnection，则当应用程序进入主事件循环时，将发送一个QEvent并调用该成员。
		如果type为Qt::BlockingQueuedConnection，该方法将以与Qt::QueuedConnection相同的方式调用，只是当前线程将阻塞，直到事件交付。
			使用这种连接类型在同一线程中的对象之间通信将导致死锁。
		如果type为Qt::AutoConnection，如果obj与调用者位于同一个线程中，则同步调用成员;否则，它将异步调用成员。

	成员函数调用的返回值放在ret中。如果调用是异步的，则不能计算返回值。您最多可以向成员函数传递十个参数(val0、val1、val2、val3、val4、val5、val6、val7、val8和val9)。

	QGenericArgument和QGenericReturnArgument是内部的辅助类。因为信号和槽可以动态调用，
	所以必须使用Q_ARG()和Q_RETURN_ARG()宏将参数括起来。Q_ARG()接受类型名和该类型的const引用;Q_RETURN_ARG()接受一个类型名和一个非const引用。


*/
//QMetaObject::invokeMethod:
class Person : public QObject
{
	Q_OBJECT

private:
	int m_age;
	QString m_name;

	void show()
	{
		qInfo() << m_age << m_name;

	}

protected:
	void clear()
	{
		m_age = 0;
		m_name.clear();
		qInfo() << __FUNCSIG__;
	}

public:
	Person(int age,const QString& name)
		:m_age(age)
		,m_name(name)
	{
	}

	void setage(int age)
	{
		m_age = age;
	}
	int age()
	{
		return m_age;
	}

	Q_INVOKABLE void setname(const QString& name)
	{
		m_name = name;
	}
	Q_INVOKABLE QString name()
	{
		return m_name;
	}
};

int main(int argc, char* argv[])
{
	QApplication a(argc, argv);

	Person person(17,"李飞");
	qInfo() << person.name();


	QMetaObject::invokeMethod(&person,"setname",Q_ARG(QString, "李琪")/*传递变量，变量类型是QString，变量数值是"李琪" */);
			//注意要在被调用的函数的前面加上	Q_INVOKABLE	以此来用	QMetaObject::invokeMethod	调用这个函数；	
			//如果你看到	QMetaObject::invokeMethod: No such method	这个字眼，那就是你没有在函数前添加Q_INVOKABLE;
	qInfo() << person.name();



	//定义一个变量来承接返回值：
	QString name;
	QMetaObject::invokeMethod(&person, "name", 
		Q_RETURN_ARG(QString,name)	/*承接返回变量，定义变量类型是QString，把返回值赋值给name */);
	qInfo() << name;

	return a.exec();
}



//显示的告诉moc 这个文件需要元编译		一定要有！！
#include "main.moc"
